qemu-img 管理虚拟机硬盘

·

虚拟机快照链(snapshot chains)

  • 虚拟机快照保存了虚拟机在某个指定时间点的状态(包括操作系统和所有的程序),
  • 利用快照,我们可以恢复虚拟机到某个以前的状态,比如测试软件的时候经常需要回滚系统。
  • 快照链就是多个快照组成的关系链,这些快照按照创建时间排列成链,
  • 像下面这样,本文章要解释的就是怎么创建这条链,链中快照的相互关系,缩短链,以及如何利用这条链回滚我们的虚拟机到某个状态
base-image<--guest1<--snap1<--snap2<--snap3<--snap4<--当前(active)
  • 如上,base-image是制作好的一个qcow2格式的磁盘镜像文件,它包含有完整的OS以及引导程序,
  • 现在以这个base-image为模板创建多个虚拟机,简单点方法,每创建一个虚拟机我们就把这个镜像完整复制一份,但这种做法效率底下,满足不了生产需要,
  • 这时就用到了qcow2镜像的特性copy-on-write
  • qcow2(qemu copy-on-write)格式镜像支持快照,具有创建一个base-image,以及在base-image(backing file)基础上创建多个copy-on-write overlays镜像的能力,
  • 解释下backing file和overlay, 我们为base-image创建一个guest1,那么此时base-image就是guest1的backing file,guest1就是base-image的overlay
  • 同理,为guest1虚拟机创建了一个快照snap1,此时guest1就是snap1的backing file,snap1是guest1的overlay,backing files和overlays十分有用,可以快速的创建瘦装备实例,特别是在开发测试过程中可以快速回滚到之前某个状态
# 使用模板镜像centosbase(backing file)创建两个虚拟机(基于centosbase),20G不是必须的参数
qemu-img create -b centosbase.qcow2 -f qcow2 centos1.qcow2 20G
qemu-img create -b centosbase.qcow2 -f qcow2 centos2.qcow2 20G
# 现在创建出来的centos1和centos2都可以用来启动一个虚拟机,因为他们依赖于backing file,所以这两个磁盘只有几百个字节大小,只有新的文件才会被写入此磁盘
# 查看镜像信息,包括虚拟磁盘大小,实际占用空间,backing file
qemu-img info centos1.qcow2

内置快照介绍(Internal Snapshots)

内置磁盘快照

单个qcow2镜像文件存储快照点的磁盘状态,没有新磁盘文件产生,虚拟机运行状态和关闭状态都可以创建,Libvirt 使用 'qemu-img' 命令创建关机状态的磁盘快照.

内置系统还原点

  • 使用virsh save/restore命令
  • 可以在虚机开机状态下(内存)保存内存状态,设备状态和磁盘状态到一个指定文件中,还原的时候虚机关机,然后restore回去
  • 多用于测试场景中,我们经常需要不断的将vm还原到某个起点,然后重新开始部署和测试。
#创建快照1(centos1运行时)
virsh snapshot-create-as centos1 centos1_sn1 centos1_sn1-desc
#创建快照2(centos1关闭)
virsh shutdown centos1
virsh snapshot-create-as centos1 centos1_sn2 centos1_sn2-desc
#查看所有快照
virsh snapshot-list centos1
#快照回滚
virsh snapshot-revert --domain centos1 centos1_sn1
virsh snapshot-revert --domain centos1 centos1_sn3
#内置磁盘快照可以随意回滚,比如先回滚到sn1,在回滚到sn3都是OK的
#注意一点是虚拟机开启状态下,不能回滚到State为running的快照点
#快照删除
virsh snapshot-delete centos1 centos1_sn2
或者
virsh snapshot-delete --domain centos1 --snapshotname centos1_sn2

外置快照介绍(External Snapshots)

外置磁盘快照(External disk snapshot)

当一个快照被创建时,创建时当前的状态保存在当前使用的磁盘文件中,即成为一个backing file,此时一个新的overlay被创建出来保存以后写入的数据

外置系统还原点(External system checkpoint)

虚拟机的磁盘状态将被保存到一个文件中,内存和设备的状态将被保存到另外一个新的文件中

# 启动centos2虚拟机,查看当前所使用磁盘
virsh start centos2
virsh domblklist centos2

virsh snapshot-create-as --domain centos2 centos2_sn1 centos2_sn1-desc --disk-only --diskspec vda,snapshot=external,file=/data_lij/vhosts/centos2_sn1.qcow2 --atomic
#查看快照
virsh snapshot-list centos2
#查看centos2当前所使用磁盘
virsh domblklist centos2
#所使用磁盘已经更新到新创建的磁盘

外置磁盘快照的合并

  • 外置快照非常有用,但这里有一个问题就是如何合并快照文件来缩短链的长度,不能直接删除某个快照,因为每个快照都保存有相应的数据
  • 有两种方式实现
    • blockcommit: 从 top 合并数据到 base (即合并overlays至backing files)
    • blockpull: 将backing file数据合并至overlay中.从 base 到 top

blockcommit向下合并

# blockcommit可以让你将'top'镜像(在同一条backing file链中)合并至底层的'base'镜像,
# 一旦 blockcommit #执行完成,处于最上面的overlay链将被自动指向到底层的overlay或base, 后用来缩短链长度的时候十分有用.
# A qemu1.3以下版本不支持live blockcommit,
# B qemu2.0以下版本不支持合并'Active'层(最顶部的overlay,即当前使用磁盘)至backing_files
# 当前: [base] <- sn1 <- sn2 <- sn3 <- sn4(当前使用磁盘)
# 目标: [base] <- sn1 <--------------- sn4
# 我们需要做的是合并sn2,sn3到sn1中,并删除sn2,sn3快照,下面有两种方式
# (method-a):
virsh blockcommit --domain f17 vda --base /export/vmimages/sn1.qcow2 --top /export/vmimages/sn3.qcow2 --wait --verbose
# 或者 (method-b):
virsh blockcommit --domain centos2 vda --base centos2_sn2.qcow2 --top centos2_sn3.qcow2 --wait --verbose
virsh blockcommit --domain centos2 vda --base centos2_sn1.qcow2 --top centos2_sn2.qcow2 --wait --verbose
# 注: 如果手工执行*qemu-img*命令完成的话, 现在还只能用method-b. 我们还可以让centos2_sn4(active)直接指向centos2

blockpull向上合并

# blockpull(qemu中也称作'block stream')可以将backing合并至active,与blockcommit正好相反.
# 在qemu最新版本2.1.2上测试发现,当前只能将backing file合并至正在使用的active中
# centosbase <-- centos2 <-- centos2_sn1 <-- centos2_sn2 <-- centos2_sn3 <-- centos2_sn4(active)
# 在上面快照链中,可以合并sn1/sn2/sn3到sn4(active),但不能合并sn1/sn2到sn3,因为sn3非当前active磁盘
# 我们需要做的是合并sn1,sn2,sn3到sn4(active)中,并删除sn1,sn2,sn3快照,如下表示关系
# 当前: [base(centos2)] <- sn1 <- sn2 <- sn3 <- sn4(当前使用磁盘)
# 目标: [base(centos2)] <---------------------- sn4
virsh blockpull --domain centos_2 --path /data_lij/vhosts/centos2_sn4.qcow2 --base /data_lij/vhosts/centos2.qcow2 --wait --verbose
#清理掉不用的快照
virsh snapshot-delete --domain centos2 centos2_sn1 --metadata
virsh snapshot-delete --domain centos2 centos2_sn2 --metadata
virsh snapshot-delete --domain centos2 centos2_sn3 --metadata
#查看centos2的快照
virsh snapshot-list centos2
#查看centos2当前使用磁盘
virsh domblklist centos2
#查看快照sn4的backing file
qemu-img info centos2_sn4.qcow2
#如果要迁移虚拟机centos2,可能要将所有backing files合并至centos2_sn4(active),然后迁移centos2_sn4(active)至目的位置
#所有的backing files 都合并到centos2_sn4(active)
virsh blockpull --domain centos2 --path /data_lij/vhosts/centos2_sn4.qcow2 --wait --verbose
qemu-img info centos2_sn4.qcow2
#合并之后,centos2_sn4是一个完整的镜像,包括centosbase,sn1/2/3所有的数据,此时其不再需要backing files

qemu-img 外置快照合并

qemu-img rebase -u -b centos2_sn1.qcow2 centos2_sn3.qcow2     #让sn3指向sn1
# 现在sn1中包含了之前的sn1/sn2中的数据,所以此时不再需要sn2中的数据,直接让sn3指向sn1即可,可以直接删除sn2
# 注意: -u代表'Unsafe mode' -- 此模式下仅仅修改了指向到的backing file名字(不复制数据)
qemu-img rebase -b centos2_sn1.qcow2 centos2_sn3.qcow2
#未使用-u模式的rebase将把数据也一并合并过去,即把sn2的数据写入到sn3并修改sn3指向sn1,此为默认模式

# 创建一个全新的、无 backing file 的镜像 (推荐)
sudo qemu-img convert -f qcow2 -O qcow2 \
  /var/lib/libvirt/images/107/vm-107-disk-0.qcow2 \
  /var/lib/libvirt/images/107/vm-107-disk-0-standalone.qcow2

# ❌ 危险!这是把当前层写回 backing file,会污染 base 镜像!
sudo qemu-img commit /var/lib/libvirt/images/107/vm-107-disk-0.qcow2

# 将backing file数据合并到当前镜像
sudo qemu-img rebase -b "" /var/lib/libvirt/images/107/vm-107-disk-0.qcow2

# 安装必要的工具(如果尚未安装)
sudo apt-get install libguestfs-tools  # Ubuntu/Debian
# 合并并优化镜像
sudo virt-sparsify --compress /var/lib/libvirt/images/107/vm-107-disk-0.qcow2 /var/lib/libvirt/images/107/vm-107-disk-0-merged.qcow2

启动失败后修复

在宿主机上挂载新镜像,更新 fstab + 重建 initramfs

# 安装必要工具(如未安装)
sudo apt install libguestfs-tools   # Debian/Ubuntu
# 或 yum install libguestfs-tools   # RHEL/CentOS
# 使用 guestfish 挂载并检查/修复
sudo guestfish --rw -a /var/lib/libvirt/images/107/vm-107-disk-0-standalone.qcow2
> run
> list-filesystems
> mount /dev/sda1 /        # 假设 / 是 sda1(根据你的分区调整!)
> mount /dev/sdb1 /mnt     # 如果 /home 或其他在 sdb1,按需挂
# 查看当前 UUID(关键!)
> sh "blkid"
# ✅ 更新 fstab 中的 UUID 为新值
> sh "sed -i 's/old-xxxx-yyyy-zzzz/new-1111-2222-3333/g' /etc/fstab"
# —— 如果是 Debian/Ubuntu:
> sh "chroot / update-initramfs -u -k all"
# 退出
> exit

临时绕过(仅用于诊断或紧急恢复) 在 GRUB 启动界面: e(edit)

添加启动参数  init=/bin/bash

mount -o remount,rw /
cat /etc/fstab
lsblk
blkid
mount -a

exec /sbin/init
systemctl set-default graphical
update-grub
reboot

启动虚拟机的剪切板共享

sudo apt install spice-vdagent
sudo virsh dumpxml debian12 | grep graphics
sudo virsh domdisplay debian12